Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 <<   zurück
Visual Basic 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual Basic 2005

Visual Basic 2005
1.233 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-585-1
gp Kapitel 14 Assemblies und Reflexion
  gp 14.1 Assemblies
    gp 14.1.1 Die Struktur einer Assembly
    gp 14.1.2 Einzeldatei-Assemblies
    gp 14.1.3 Mehrfachdatei-Assemblies
    gp 14.1.4 Private und globale Assemblies
    gp 14.1.5 Festlegen der Assembly-Eigenschaften
  gp 14.2 Die Metadaten mittels Reflexion abfragen
    gp 14.2.1 Was ist Reflexion?
    gp 14.2.2 Der Mittelpunkt der Reflexion: die Klasse »Type«

Kapitel 14 Assemblies und Reflexion


Galileo Computing

14.1 Assemblies  downtop

Eine .NET-Anwendung ist eine funktionale, logische Einheit, bestehend aus einer oder mehreren Assemblies. Wenn Sie in der Entwicklungsumgebung das Menü Erstellen wählen oder in der Symbolleiste die Start-Schaltfläche anklicken, erzeugt der Compiler automatisch immer eine Assembly. Ob es sich um eine ausführbare Datei mit der Endung EXE oder um eine DLL-Datei handelt, hängt vom gewählten Projekttyp ab. Entwickeln Sie eine Konsolen-, Windows- oder Windows-Dienstanwendung, wird eine EXE-Datei erzeugt, ist das Projekt vom Typ Klassen- oder Steuerelementbibliothek, wird eine DLL-Datei generiert. Die Kompilate werden, abhängig von der Konfigurationseinstellung, im Ordner /bin/Debug bzw. obj/Debug unterhalb des Projektordners gespeichert.

Für etwas Verwirrung könnten Dateien mit der Endung DLL sorgen. Ursprünglich als reine Funktionssammlungen gedacht (denken Sie beispielsweise an die Betriebssystemfunktionen der Win32-API oder die Funktionen in der ODBC-API), wurde dieselbe Dateierweiterung später auch für COM-basierte InProc-Server benutzt. Jetzt kommt der nächste Typus hinzu, der auf die Common Language Runtime angewiesen ist. Zumindest eins ist allen DLL-Typen gemein: Sie sind, um sie ausführen zu können, immer auf die Unterstützung einer ausführbaren EXE-Datei angewiesen.

Softwarekomponenten allgemein verfügbar auf einem Rechner abzulegen, ist ein Ziel, das viele Hersteller anstreben. Microsoft hatte in den 90er-Jahren dazu eine Technologie entwickelt, die sich über viele Jahre hinweg auf den Windows-Plattformen etablierte und im Laufe der Zeit immer mächtiger und komplexer wurde: COM und DCOM. Diese schnittstellenorientierten Technologien beschreiben, wie Softwarekomponenten miteinander kommunizieren, auch wenn sie in unterschiedlichen Programmiersprachen realisiert worden sind. Der Datenaustausch musste dabei über ein COM-spezifisches, von den Programmiersprachen unabhängiges Typsystem abgewickelt werden.

Das war aber nicht das einzige Problem, das COM/DCOM bereitete. Ein ganz wesentlicher Nachteil war die Trennung des Programmcodes von seiner Selbstbeschreibung, die in der Typbibliothek der Registrierungsdatenbank zu finden ist. Außerdem war es unmöglich, versionsunterschiedliche COM-Komponenten gleichzeitig auf einem Rechner zu installieren. Das führte in der Vergangenheit häufig dazu, dass Programme, die für den Zugriff auf eine ältere Komponentenversion programmiert waren, ihren Dienst quittierten, wenn mit einem anderen Programm eine neuere Komponentenversion installiert wurde.

Um diesen Kreislauf aufzubrechen, wurde ein völlig neues Konzept spezifiziert, das die folgenden Forderungen definierte:

gp  Eine Anwendung muss ihre Dienste selbst beschreiben können, ohne von anderen Systemdiensten wie der Registrierungsdatenbank abhängig zu sein. Dementsprechend müssen Code und Selbstbeschreibung einer Komponente eine kompakte Einheit bilden.
gp  Zur Vermeidung von Versionskonflikten müssen mehrere Versionen einer Komponente parallel installiert werden können. Damit wird gewährleistet, dass eine Anwendung, die sich der Dienste einer Komponente bedient, nicht durch die Installation einer neuen, jedoch inkompatiblen Komponente in das laufzeittechnische Nirwana befördert wird.
gp  Die verschiedenen Versionen einer Softwarekomponente müssen gleichzeitig ausführbar sein. Da eine neu zu verteilende Komponentenversion durchaus auch nur das Ziel haben kann, einen bekannt gewordenen Fehler zu beseitigen, sollte eine von dieser Komponente abhängige Anwendung in der Lage sein, aus einer Vielzahl gleicher, jedoch versionsverschiedener Komponenten diejenige zu finden, mit der problemlos zusammengearbeitet werden kann.
gp  Es muss sichergestellt werden, dass die von einer Anwendung geforderte, richtige Version der Komponente geladen und ausgeführt wird.

Bei allen genannten Punkten setzt das Konzept der Assemblies an. Mehrere Versionen derselben Softwarekomponente dürfen auf einer Maschine installiert sein – mehr noch, sie dürfen sogar gleichzeitig ausgeführt werden. Damit wird zwar einerseits das Prinzip der Abwärtskompatibilität aufgegeben, das unter COM eine elementare Forderung war, andererseits ist Abwärtskompatibilität auch nicht mehr notwendig, weil an eine Anwendung ebenfalls Forderungen hinsichtlich des Komponentenzugriffs gestellt werden.

Die Selbstbeschreibung einer COM-Komponente erfolgt in der Typbibliothek, die als eigenständige Einheit getrennt vom Binärcode existiert. Die Beschreibung einer Assembly samt ihrer internen Komponenten erfolgt in einem Block, der als Manifest bezeichnet wird und mit dem Code unzertrennlich verbunden ist.

Eine Assembly lässt sich nicht nur als die Baugruppe einer Anwendung begreifen, sie bildet daneben auch gleichzeitig die Einheit, die verteilt wird, beschreibt Sicherheitsrichtlinien und bildet die Basis der Versionierung. Sie ist das Element, mit dem die Common Language Runtime umzugehen versteht.

Einzel- und Mehrfachdatei-Assemblies

Hinsichtlich dessen, was von einer Assembly beschrieben wird, muss man zwischen zwei Typen unterschieden:

gp  Einzeldatei-Assemblies, die nur aus einer Datei gebildet werden
gp  Mehrfachdatei-Assemblies, die einen Verbund aus mehreren Dateien logisch zusammenfassen

Mehrfachdatei-Assemblies bieten sich dann an, wenn ein Programm über das Web geladen wird. Die .NET-Laufzeitumgebung lädt nur dann eine Datei, wenn im aktuell ausgeführten Code auf einen Typ aus einer anderen Datei verwiesen wird. Das Auslagern von Typen in einer eigenen Datei wirkt sich positiv auf die Gesamtperformanz einer Anwendung aus und schont die Systemressourcen so lange, bis ein Objekt dieses Typs benötigt wird – falls das überhaupt jemals während Laufzeit der Fall ist. Die Effizienz einer solchen Assembly kann sich dadurch gegebenenfalls verbessern.

Zugriff auf Assemblies

Die Frage, die bei jeder Anwendungsentwicklung neu gestellt werden muss, ist, ob der Code in einer DLL-Assembly nur einer Anwendung zugänglich sein soll oder ob auch andere Programme darauf zugreifen dürfen. Im letzteren Fall unterliegt die Assembly festgelegten Versionsrichtlinien, die jedem Client den fehlerfreien Zugriff auf die jüngste kompatible Version ermöglichen.

Aus der Fragestellung, wie und von wem eine Assemblierung genutzt werden darf, folgt die Definition zweier unterschiedlicher Assembly-Typen:

1. Private Assemblies, die nur von einer Anwendung genutzt werden können.
       
2. Gemeinsame Assemblies (globale Assemblies), die allen Anwendungen gleichermaßen ihre Dienste offen legen.
       

Eine private Assembly zu entwickeln, ist denkbar einfach: Man muss nichts Besonderes dafür tun, private Assemblies sind der Standard. Wollen Sie eine Assembly zur allgemeinen Nutzung veröffentlichen, ist ein kryptografischer Schlüssel erforderlich, der gewährleistet, dass eine Assembly von einer anderen, zufälligerweise gleichnamigen Assembly eines anderen Entwicklers eindeutig unterschieden werden kann.


Galileo Computing

14.1.1 Die Struktur einer Assembly  downtop

Allgemeine Beschreibung

Eine Assembly muss vielen Anforderungen gerecht werden, um die gesteckten Ziele einer einfachen und sicheren Versionierung und Verteilung zu erreichen. Der wesentlichste Punkt, und damit auch das größte Unterscheidungsmerkmal im Vergleich zu den COM-Komponenten, ist die Zusammenfassung von Code und Selbstbeschreibung.

Überlegen wir, was zu einer Selbstbeschreibung alles gehört:

gp  Der Name, um die Assembly zu identifizieren.
gp  Informationen, um anderen Assemblies mitzuteilen, ob die vorliegende Assembly das Original oder eine neuere Version ist. Damit ist auch die Frage verbunden, ob die neue Version gegebenenfalls abwärtskompatibel ist oder nicht.
gp  Eine Assembly muss Informationen darüber haben, von welchen anderen Komponenten sie abhängt. Dazu gehören unter anderem der Name und die Versionsnummer der von ihr referenzierten Assemblies.
gp  Informationen über die von der Assembly exportierten Typen.
gp  Die Bezeichner aller Methoden, einschließlich der Parameternamen und -typen, sowie der Typ des Rückgabewertes.

Diese Punkte lassen sich in zwei logische Kategorien zusammenfassen:

gp  Metadaten, die eine Assembly ganzheitlich beschreiben und als Manifest der Assembly bezeichnet werden
gp  Daten zur Beschreibung des IL-Codes, die Typmetadaten

Metadaten sind Daten, die andere Daten beschreiben. Wenn Ihnen das zu abstrakt ist, denken Sie an die Tabelle einer Datenbank. Die Entitäten (Spalten bzw. Felder) der Tabelle werden ebenfalls über Metadaten beschrieben. Dazu gehören beispielsweise die Typdefinition eines Feldes, Gültigkeitsregeln, Standardwerte usw.

Selbst die einfachste Assemblierung setzt sich aus mindestens drei Blöcken zusammen:

gp  aus den Metadaten, welche die Assembly allgemein beschreiben (das Manifest)
gp  aus den Typmetadaten, welche die öffentlichen Typen beschreiben
gp  aus dem IL-Code

Assemblies können nicht nur Module für Typen enthalten, oft gehören auch Ressourcen dazu, beispielsweise BMP-, JPEG- und HTML-Dateien, die von der Assembly zur Laufzeit benötigt werden. Diese Dateien sind dann ebenfalls Bestandteil einer Assembly. Es lässt sich auch eine Assembly vorstellen, die weder Code noch Ressourcen enthält – ob eine solche Assembly allerdings noch Sinn macht, lassen wir dahingestellt. Was letztendlich eine Assembly erst zu einer solchen werden lässt, sind die Metadaten über sich selber, also das Manifest.

In einer einfachen Assembly sind alle Informationen in einer EXE- oder DLL-Datei enthalten, wie in Abbildung 14.1 am Beispiel der fiktiven Assembly GraphicObjects.dll dargestellt.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.1     Struktur einer Einzeldatei-Assembly

Eine Mehrfachdatei-Assembly ist strukturell komplexer aufgebaut. In welcher Kombination IL-Code und Ressourcen zusammengefasst werden, steht dem Entwickler frei und orientiert sich an Gesichtspunkten, die später noch erörtert werden. Unabhängig davon, aus wie vielen Dateien die Assemblierung gebildet wird, kann sich das Manifest nur in einer Datei befinden. Ob diese Datei auch noch IL-Code enthält oder nicht, spielt keine Rolle. Sie können durchaus für das Manifest eine eigene Datei vorsehen und den IL-Code in zumindest einer weiteren verpacken. Die Typmetadaten sind aber auf jeden Fall an den Code gebunden und somit in derselben Datei enthalten.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.2     Struktur einer Mehrfachdatei-Assembly

Abbildung 14.2 zeigt dieselbe Anwendung wie Abbildung 14.1, diesmal allerdings zu einer Mehrfachdatei-Assembly kompiliert. Der Programmcode ist in drei Dateien enthalten, zusätzlich bindet das Manifest noch eine Bitmap-Datei als Ressourcendatei ein. Beachten Sie bitte, dass das Manifest, die Metadaten der Assemblierung, nur in einer Datei enthalten sein kann – in der Abbildung ist es die Datei GraphicObjects.dll. Für eine von außen zugreifende Komponente bildet diese Datei damit auch gleichzeitig den Einstiegspunkt in die Assemblierung. Die Dateierweiterung der beiden anderen codeenthaltenden Dateien lautet .NETMODULE. Diese Dateien enthalten zwar Typdefinitionen, jedoch niemals das Manifest.

Manifest und Metadaten

Metadaten sind binäre Informationen, die beim Kompilieren einer Datei, sei es in eine DLL- oder EXE-Datei, hinzugefügt werden und die Daten ganzheitlich beschreiben. Jeder Typ, den man innerhalb einer Assembly definiert oder einbindet, wird von den Metadaten erfasst. Zur Laufzeit einer Assembly werden die Metadaten in den Speicher geladen und von der Common Language Runtime dazu benutzt, die benötigten Informationen zu beziehen, die zur Erstellung und Verwendung eines Objekts erforderlich sind.

Der Informationsgehalt der Metadaten ist vielseitiger Natur und lässt sich in zwei Gruppen einteilen:

gp  Das Manifest, das die Struktur einer Assembly beschreibt. Zu dem Informationsgehalt eines Manifests gehören:
    gp  der Typname
    gp  die Versionsnummer
    gp  der öffentliche Schlüssel
    gp  die Liste aller Dateien, aus denen sich die Assembly zusammensetzt
    gp  die Liste aller weiteren Assemblies, die an die aktuelle statisch gebunden sind
    gp  Sicherheitsrichtlinien, welche die Berechtigungen an der Assembly steuern
gp  Typmetadaten, welche die Typen innerhalb eines Moduls beschreiben. Das schließt den Namen des Typs, seine Sichtbarkeit, seine Basisklassen und die von ihm implementierten Schnittstellen ein.
Mit dem Manifest und den Typmetadaten verfügt die Common Language Runtime über genügend Informationen, um Klassen aus einer Datei zu laden, Objekte zu erstellen, Methodenaufrufe aufzulösen und auf Objektdaten zuzugreifen.

Es spielt auch keine Rolle, in welcher Sprache die Bausteine einer Assembly entwickelt worden sind: Das Manifest verwischt die Spuren des zugrunde liegenden Quellcodes. Unter COM war zur binären Bindung eines Servers an einen Client noch Bindecode notwendig (IDL – Interface Definition Language), um eine gemeinsame Basis für die Kommunikation und den Datenaustausch beider Komponenten zu schaffen. Die Intermediate Language (IL) und die Common Language Runtime (CLR) schaffen mittels des Manifests die Voraussetzung für den problemlosen Austausch ohne den Aufbau solcher Behelfsbrücken.

Der IL-Disassembler

Sie können sich die Metadaten einer Assembly vom Typ EXE oder DLL sowie die der Module mit der Erweiterung NETMODULE ansehen, wenn Sie das mit Visual Studio 2005 gelieferte Tool ildasm.exe, den so genannten IL-Disassembler, an der Konsole aufrufen. Sie finden diese Datei in einem Unterordner der Visual Studio-Installation. Haben Sie die Standardvorgaben bei der Installation übernommen, wird es sich um

\Programme\Microsoft Visual Studio 8\SDK\v2.0\bin

handeln.

Optional geben Sie beim Aufruf des Tools den Pfad zu einer Assembly an, beispielsweise:


ildasm C:\MeineProjekte\MyFirstAssembly.exe

Starten Sie den Disassembler ohne Dateiangabe, können Sie über das Menü Datei N Öffnen die zu inspizierende Assembly wählen.

Wir wollen uns nun mit Hilfe des ILDASM-Tools das Manifest einer Konsolenanwendung ansehen, die neben dem Ausgabecode der Methode Main in derselben Quellcodedatei noch die Definition der Klasse ClassA enthält. In einer zweiten Quellcodedatei des Projekts ist die Klasse ClassB definiert. Um den Informationsgehalt im Disassembler zu verdeutlichen, enthält der Typ ClassB insgesamt drei Variablendeklarationen mit unterschiedlichen Sichtbarkeiten.


' die Klasse ClassA und Module1 sind in der Quellcodedatei
' Module1.vb definiert
Module Module1
Sub Main()
Dim col As DataColumn = New DataColumn()
Console.WriteLine("Hallo Welt.")
Console.ReadLine()
End Sub
End Module
Public Class ClassA
Public intVar As Integer
End Class
' Klasse ClassB ist in der Quellcodedatei ClassB.vb definiert
Public Class ClassB
Public intVarint As Integer
Private lngVar As Long
Protected strText As String
End Class

Der Name der Assembly sei MyAssembly. Beachten Sie bitte, dass in Main zu Demonstrationszwecken ein Objekt vom Typ DataColumn erstellt wird, also auf die standardmäßig eingebundene Datei System.Data.dll zugegriffen wird. Sehen wir uns jetzt an, was uns das ILDASM-Tool liefert, ohne dabei allzu sehr in die Details zu gehen.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.3     Die Anzeige des ISDASM-Tools

Unterhalb des Wurzelknotens, der den Pfad zu der Assemblierung angibt, ist – mit einem roten Dreieck gekennzeichnet – das Manifest angeführt. Darunter befindet sich der Knoten MyAssembly, der in der Abbildung bereits vollständig geöffnet ist. Das blaue Rechteck, das mit seinen drei nach rechts weisenden Linien an einen Stecker erinnert, symbolisiert Klassendefinitionen.

Neben den Typmetadaten listet das Tool alle Variablen und Klassenmethoden auf – in unserem Beispiel nur die statische Methode Main aus Module1 sowie die mit .ctor bezeichneten Konstruktoren – und gibt den Sichtbarkeitsbereich der Variablen an. Der Rückgabewert der Methoden wird, getrennt durch einen Doppelpunkt, hinter dem Methodennamen angeführt.

Unter dem Knoten MyAssembly.My ist der Visual Basic-spezifische My-namespace zu erkennen. Über dieses mit Visual Basic 2005 eingeführte Feature haben wir noch nicht gesprochen, aber das wird in den kommenden Kapiteln noch nachgeholt werden.

Werfen wir jetzt einen Blick auf das Manifest dieser Assemblierung. Ein Doppelklick auf den Manifest-Eintrag des Disassemblers öffnet ein weiteres Fenster, das die Metadaten der Assemblierung wiedergibt (siehe Abbildung 14.4).

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.4     Das Manifest einer Assembly

Der Reihe nach werden alle externen Assemblies aufgelistet, von denen die aktuelle Assembly abhängt. Dazu gehört die wichtigste aller Assemblies, die mscorlib (beschrieben durch die Datei mscorlib.dll). Ihr folgt auch die Assembly, die aufgrund der Erzeugung des Objekts vom Typ DataColumn ebenfalls von der Anwendung benutzt wird: System.Data (bzw. die Datei System.Data.dll).

Der Liste der externen Assemblierungen schließt sich im Block


.assembly MyAssembly

eine Liste diverser Attribute an, mit denen die Assemblierung beschrieben wird. Die Attribute können in der Datei AssemblyInfo.vb der Entwicklungsumgebung festgelegt werden.

Sehen wir uns nun die Referenzangabe einer externen Assembly genauer an, die beispielsweise folgendermaßen lautet:


.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}

Weiter oben wurde bereits ein wesentlicher Unterschied zwischen einer privaten und einer gemeinsam genutzten, externen Assembly erwähnt: Eine gemeinsam genutzte Assembly verfügt über einen öffentlichen Schlüssel. Im Manifest wird dieser Schlüssel hinter dem Attribut .publickeytoken in geschweiften Klammern angegeben. Neben dem Namen einer Assemblierung trägt unter anderem auch der Schlüssel zur eindeutigen Identifizierung der Assembly bei und sichert gleichzeitig die Identität des Komponentenentwicklers. Auf dieses Thema werden wir später noch einmal zurückkommen.

Die .NET-Laufzeitumgebung gewährleistet zur Vermeidung von Kompatibilitätsproblemen Parallelität, d.  h., es dürfen mehrere gleichnamige, allerdings versionsunterschiedliche Assemblierungen nebeneinander existieren, ohne damit Konflikte zu verursachen. Die Versionsinformationen sind in einem standardisierten Format dargestellt, das im Manifest der referenzierenden Assemblierung mit dem Attribut .ver gekennzeichnet ist. Zur Laufzeit der Anwendung werden die Versionsinformationen von der Laufzeitumgebung zur Auflösung des angeforderten Typs umgesetzt.


Galileo Computing

14.1.2 Einzeldatei-Assemblies  downtop

Normalerweise übernimmt die Entwicklungsumgebung das Kompilieren für Sie – entweder über das Menü Erstellen oder durch Anklicken der Start-Schaltfläche in der Symbolleiste. Das Ergebnis ist dabei immer eine einfache, also aus einer Datei bestehende Assembly. Sämtliche Beispiele, die wir in diesem Buch bisher betrachtet und kompiliert haben, waren am Ende Einzeldatei-Assemblierungen. Die innere Struktur dieses Typs zeigt Abbildung 14.1.

Der Visual Basic-Compiler

VB-Quellcode lässt sich auch manuell an der Eingabekonsole durch den expliziten Aufruf des VB-Compilers vbc.exe kompilieren, der im Verzeichnis

\Windows\Microsoft.NET\Framework\v2.x.xxxx

zu finden ist. Die letzte Angabe bezieht sich auf die aktuell installierte Version des .NET Frameworks und könnte deshalb bei Ihnen unter Umständen anders lauten.

Der Aufruf des Compilers erfolgt an der Eingabeaufforderung unter Auflistung der zu kompilierenden Quellcodedateien mit der Dateiendung .VB, aus denen die Assembly erzeugt werden soll:


vbc.exe <Dateiliste>

Das Ergebnis der Kompilierung ist standardmäßig eine EXE-Datei, kann aber durch den Optionsschalter /target beeinflusst werden, um andere Dateitypen zu erstellen. Wir kommen auf dieses Thema gleich noch zurück.

Nehmen wir an, ein Projekt würde sich aus den Quellcodedateien

gp  ClassA.vb (enthält die Definition der Klasse ClassA)
gp  ClassB.vb (enthält die Definition der Klasse ClassB)

zusammensetzen. Am einfachsten haben Sie es, wenn Sie an der Konsole in das Projektverzeichnis wechseln und die folgende Anweisung eingeben:


vbc ClassA.vb ClassB.vb

Das Ergebnis der Kompilierung wird als EXE-Datei im Projektordner gespeichert. Da es sich bei dem Kompilat um eine .NET-Assembly handelt, beinhaltet die Ausgabedatei natürlich gleichzeitig auch das Manifest.

Standardmäßig orientiert sich der Name der resultierenden Programmdatei am Namen der Quellcodedatei, die im ersten Argument angegeben wird. Die obige Anweisung würde somit die Datei ClassA.exe erstellen. Sie können jedoch mit dem Optionsschalter /out den Ausgabenamen nach eigenem Ermessen festlegen. Soll die kompilierte Datei beispielsweise MyApp heißen, muss man den VB-Compiler wie folgt aufrufen:


vbc /out:MyApp.exe ClassA.vb ClassB.vb

Als Separator zwischen dem Optionsschalter und dem auszugebenden Dateinamen dient der Doppelpunkt.

Kompilierung in einen anderen Dateityp

Der Compiler versetzt uns in die Lage, eine oder mehrere VB-Dateien gleichzeitig in eine EXE-Ausgabedatei zu kompilieren. Unter gewissen Umständen ist es erforderlich, abweichend von diesem Standard einen anderen Dateityp zu kompilieren. Dazu dient der Optionsschalter /target, der auf zwei verschiedene Arten gesetzt werden kann:


/target:<Attribut>

oder optional die Kurzform


/t:<Attribut>

Unter <Attribut> kann man eins von insgesamt vier möglichen Attributen angeben, die Sie der folgenden Tabelle entnehmen können.


Tabelle 14.1     Attribute der »target«-Option des VB-Compilers

Attribut Beschreibung
exe Erzeugt eine EXE-Datei (dies ist der Standard des Compilers).
library Erzeugt eine Klassenbibliothek mit der Dateierweiterung DLL.
module Erzeugt ein Modul mit der standardmäßigen Dateierweiterung NETMODULE.
winexe Erzeugt ein Windows-Programm.

Unabhängig davon, welches Attribut Sie dem Compiler als Option angeben, wird aus der Liste der aufgeführten VB-Dateien eine Einzeldatei-Assembly generiert, die auf jeden Fall neben den Typmetadaten auch den IL-Code enthält – wenn es sich um eine EXE- oder DLL-Datei handelt, zusätzlich auch noch das Manifest.


Dem Attribut module mit der resultierenden Dateierweiterung kommt eine ganz besondere Bedeutung zu: Module dieses Typs sind die Bausteine einer Mehrfachdateiassemblierung, die nur Typmetadaten enthalten, jedoch kein Manifest. NETMODULE-Bausteine werden oft einfach nur als »Module« bezeichnet und gelten wegen des fehlenden Manifests nicht als Assembly.



Galileo Computing

14.1.3 Mehrfachdatei-Assemblies  downtop

Mehrfachdatei-Assemblies können sowohl in eigenstartfähige Anwendungen mit der Dateierweiterung EXE als auch in den Typ einer Klassenbibliothek mit der Erweiterung DLL kompiliert werden. Es muss genau einen Baustein dieses Typs geben, alle anderen Bausteine der Anwendung – zumindest die, die IL-Code enthalten – haben die Dateierweiterung NETMODULE. Möglicherweise gesellen sich dazu noch die von der Anwendung benutzten Ressourcendateien.

Eine Mehrfachdatei-Assembly bietet sich an, wenn Sie beispielsweise eine über das Web zu verteilende Anwendung entwickeln und die Effizienz des Downloads gesteigert werden soll. Eine NETMODULE-Datei wird nur dann geladen, wenn in der Anwendung ein Typ benötigt wird, der sich in dem Modul befindet. Ein Mehrfachdatei-Assembly ist auch dann in Erwägung zu ziehen, wenn mehrere Entwickler an ein und demselben Projekt arbeiten, dabei aber unterschiedliche Entwicklungssprachen benutzen.

Obwohl eine Mehrfachdatei-Assembly aus zwei oder mehr Dateien gebildet wird, enthält sie nur ein Manifest, das sich entweder als Block in eines der kompilierten Module eingliedert oder als separate Datei vorliegt. Sehen Sie sich dazu Abbildung 14.5 an. Die Assembly setzt sich aus einer NETMODULE-Datei und einer DLL-Datei zusammen. Letztere enthält neben den Typmetadaten und dem IL-Code das Manifest. Bei der Assembly2 könnte es sich um dieselbe Anwendung handeln, mit dem Unterschied, dass das Manifest von sämtlichen anderen Elementen abgekoppelt in eine eigenständige Datei kompiliert worden ist.

Die Erkenntnis, dass Codemodule während des Kompilierungsprozesses in eigenständige Dateien kompiliert werden können, führt auch zu einer grundsätzlich neuen Betrachtungsweise hinsichtlich der Strukturierung eines Projekts. Wenn Sie sich von Anfang an sicher sind, eine Mehrfachdatei-Assembly entwickeln zu wollen, kommt der Überlegung über die sinnvolle Aufteilung der Typen auf mehrere Quellcodedateien eine weit reichende Bedeutung zu. Zur Kompilierzeit können nämlich die Quellcodedaten in beliebiger Kombination sowohl in NETMODULE-Bausteine als auch in EXE- oder DLL-Dateien gepackt werden.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.5     Das Manifest in einer Mehrfachdatei-Assembly

Manifest und IL-Code in einer Datei

Das Kompilieren einer Mehrfachdatei-Assembly unterscheidet sich von der Kompilierung einer Einzeldatei-Assembly. Während bei einer einfachen Assembly ein Aufruf des VB-Compilers genügt, müssen wir bei einer Mehrfachdateiassemblierung jedes Quellcodemodul einzeln kompilieren und dabei nach bestimmten Kriterien eine gewisse Reihenfolge einhalten. Die Entscheidung, das Manifest in eine separate Datei zu kompilieren, zwingt uns sogar dazu, ein weiteres Tool des .NET Frameworks einzusetzen.

Die Entwicklung einer Mehrfachdateiassemblierung wird in diesem und dem folgenden Abschnitt im Detail gezeigt. Als Grundlage dienen dazu zwei eigenständige Projekte: Timer- App als Klassenbibliothek und ClientApp als Konsolenanwendung.

Im Projekt TimerApp wird eine Klasse namens Timer mit einer Methode ShowDateTime definiert. Diese Methode greift auf die Klassenmethode DateTime. Now zu und gibt die aktuelle Systemzeit und das Systemdatum an der Konsole aus.


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 14\MehrfachdateiAssembly\TimerApp
' ----------------------------------------------------------
Public Class Timer
Public Sub ShowDateTime()
Console.WriteLine(DateTime.Now)
End Sub
End Class

Das zweite Projekt ClientApp enthält in der Quellcodedatei Module1.vb das Modul Module1 mit der Methode Main, die als Einstiegspunkt in die Anwendung dient. In der Methode wird ein Objekt vom Typ Timer der Anwendung TimerApp erzeugt, um die Methode ShowDateTime auszuführen. Da wir auf die externe Komponente TimerApp zugreifen, muss diese zuerst unter Verweise eingebunden werden, bevor wir deren Dienste in Anspruch nehmen können.

Damit wir später mehr Kombinationsmöglichkeiten zur Generierung der Mehrfachdatei-Assembly haben, enthält ClientApp mit der Klasse SecondClass in SecondClass.vb zusätzlich ein zweites Codemodul, dessen Methode GetOutput ebenfalls aus Main heraus aufgerufen wird.


' ---------------------------------------------------------
' Beispiel: ...\Kapitel 14\MehrfachdateiAssembly\ClientApp
' ---------------------------------------------------------
Imports TimerApp
Module Module1
Sub Main()
Dim obj As New Timer
obj.ShowDateTime()
Dim newClass As New SecondClass
newClass.GetOutput()
Console.ReadLine()
End Sub
End Module
' Klassendefinition in einem eigenen Quellcodemodul
' des Projekts ClientApp
Public Class SecondClass
Public Sub GetOutput()
Console.WriteLine("In der Klasse SecondClass")
End Sub
End Class

Eine Mehrfachdatei-Assembly setzt sich aus mindestens einer NETMODULE-Datei und einer DLL- bzw. EXE-Datei zusammen – Ressourcendateien, die ebenfalls Bausteine einer Assembly sein können, lassen wir bei unserer Betrachtung außen vor. Jedes Element einer Assembly muss einzeln mit dem VB-Compiler kompiliert werden. Die Reihenfolge ist allerdings nicht beliebig, sondern folgt einer klaren Richtlinie. Sehen wir uns dazu den Quellcode von Module1 mit der Methode Main an. Wir gehen bei unseren Betrachtungen davon aus, dass alle Quellcodedateien des Projekts ClientApp in separate Dateien kompiliert werden. Würden wir versuchen, Module1 mit


vbc Module1.vb

als Erstes zu kompilieren, wäre eine Fehlermeldung des Compilers die Folge. Das Problem resultiert aus der Tatsache, dass in Main ein Objekt vom Typ Timer instanziiert wird, der Compiler jedoch keine für ihn verwertbaren Typinformationen über Timer vorfindet. Es ist daher unumgänglich, zuerst die Bausteine einer Assembly zu kompilieren, die keinen Bezug zu anderen in der Anwendung definierten Typen haben.

Die Folge ist, dass wir zuerst Timer.vb kompilieren müssen. Da wir als Ergebnis eine Datei mit der Erweiterung NETMODULE benötigen, greifen wir auf die Option target des VB-Compilers zurück und geben das Attribut module an:


vbc /target:module Timer.vb

Prinzipiell ist diese Anweisung richtig, erfordert aber einige Anmerkungen:

1. Die Dateien, aus denen sich eine Mehrfachdateiassemblierung zusammensetzt, müssen sich alle im gleichen Verzeichnis befinden. Empfehlenswert ist es deshalb, vor der ersten Kompilierung einen eigenen Ordner für die komplette Assembly anzulegen.
       
2. Die Kompilate werden genau in das Verzeichnis geschrieben, das der aktuellen Position der Eingabeaufforderung entspricht. Daher muss man nach dem Öffnen der Konsole zuerst in das entsprechende Verzeichnis wechseln.
       
3. Befindet sich das Quellcodemodul in einem Verzeichnis, das nicht der aktuellen Position der Eingabeaufforderung entspricht, müssen Sie den kompletten Pfad zu der Datei angeben.
       

Gehen wir für die folgenden Beispiele von der Annahme aus, dass wir die die Assembly bildenden Dateien in den Ordner C:\MyProject schreiben wollen. Die projektbildenden Dateien sollen sich in den Verzeichnissen C:\TimerApp und C:\ClientApp befinden.

Nach dem Öffnen des Konsolenfensters müssen wir als Erstes in das Anwendungsverzeichnis C:\MyProject wechseln und können dann die folgende Anweisung geben:


vbc /target:module C:\TimerApp\Timer.vb

Solange wir die korrekten Pfadangaben machen und sich der VB-Compiler in einem bekannten Suchpfad befindet, ist an dieser Anweisung nichts auszusetzen. Beabsichtigen wir, auch für die Quellcodedatei SecondClass.vb einen eigenen Baustein zu kompilieren, wird nun auch


vbc /target:module C:\ClientApp\SecondClass.vb

fehlerfrei ausgeführt. Damit liegen im Verzeichnis C:\MyProject bereits die beiden kompilierten Dateien

gp  SecondClass.netmodule
gp  Timer.netmodule

vor, die beide die für sie spezifischen Typmetadaten und natürlich den IL-Code enthalten. Zum Abschluss fehlt noch die ausführbare EXE-Datei, die aus Module1.vb generiert wird und das Manifest enthalten soll. Diese wird auch mit VB-Compiler erzeugt, allerdings muss dabei berücksichtigt werden, dass es zwei Abhängigkeiten gibt – nämlich die von den beiden zuvor kompilierten NETMODULE-Bausteinen.

Um den Compiler nicht im Ungewissen zu lassen, welche Dateien zu einer Assemblierung gehören, setzt man einen anderen Optionsschalter des VB-Compilers: /addmodule. Vom Optionsschalter durch einen Doppelpunkt getrennt gibt man dahinter die Liste der Dateien an, deren Typinformationen zum fehlerfreien Kompilieren der aktuellen Datei erforderlich sind, z.  B.:


vbc /addmodule:Timer.netmodule,SecondClass.netmodule C:\ClientApp\Module1.vb

Beachten Sie, dass die einzelnen Module durch ein Komma getrennt werden. Nun haben Sie bereits Ihre erste Mehrfachdateiassemblierung erstellt, die sich aus drei Dateien zusammensetzt:

gp  Module1.exe (diese Datei beinhaltet auch das Manifest)
gp  Timer.netmodule
gp  SecondClass.netmodule

Wollen Sie die Anwendung verteilen, müssen Sie selbstverständlich alle drei Dateien an den Empfänger weitergeben.

Sehen wir uns zum Abschluss noch das Manifest der Mehrfachdatei-Assembly an (siehe Abbildung 14.6).

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.6     Das Manifest der Mehrfachdatei-Assembly »Module1.exe«

Der Bezug der Assemblierung zu den Codemodulen wird durch zwei Einträge festgehalten. Jedes Codemodul, das an der Bildung der Assemblierung beteiligt ist, wird mit einem Hash-Code beschrieben, ebenso die Typen Timer und SecondClass mit der Angabe, in welchem Baustein der Typ enthalten ist.

Zusammenfassung der Kompilierungsschritte

Fassen wir an dieser Stelle noch einmal alle Schritte zusammen, die notwendig sind, um eine Mehrfachdatei-Assembly zu erzeugen, deren Manifest sich nicht in einer eigenen Datei befindet:

1. Zuerst werden alle VB-Quellcodedateien kompiliert, die das Manifest nicht enthalten und außerdem keine Referenz auf einen Typ in einem anderen Baustein der Assemblierung haben. Das Ergebnis ist in jedem Fall eine NETMODULE-Datei, die nur den IL-Code und die Typmetadaten enthält. In der Anweisung zur Kompilierung dieser Bausteine wird der Optionsschalter /target:module des VB-Compilers gesetzt.
       
2. Im zweiten Schritt kompiliert man die Bausteine zu NETMODULE-Dateien, die einen Typ in einem anderen Baustein referenzieren. Neben dem Optionsschalter /target kommt bei diesen Modulen auch der Schalter /addmodule zum Tragen.
       
3. Im letzten Schritt wird eine EXE- oder DLL-Datei kompiliert, die das Manifest enthält. Dabei werden alle an der Assemblierung beteiligten Module mit /addmodule zu einer logischen Einheit zusammengefügt.
       

Das Manifest in einer separaten Datei

In diesem Abschnitt soll gezeigt werden, wie eine Assembly erstellt wird, deren Manifest in einer separaten Datei vorliegt. Die Schritte dazu unterscheiden sich von denen, die im letzten Abschnitt besprochen worden sind, weil sich dazu nicht mehr der VB-Compiler eignet, sondern ein anderes Tool des .NET Frameworks zum Einsatz kommt.

Zunächst sind wiederum zuerst die Quellcodemodule Timer.vb und SecondClass.vb in NETMODULE-Dateien zu kompilieren. Die Anweisungen dazu sind dieselben wie im letzten Abschnitt:


vbc /target:module C:\TimerApp\Timer.vb
vbc /target:module C:\ClientApp\SecondClass.vb

Da für das Manifest jetzt eine eigene Datei vorgesehen ist, wird die Datei Module1.vb ebenfalls in eine NETMODULE-Datei kompiliert. Dabei muss die Bindung an das Modul Timer.netmodule berücksichtigt werden:


vbc /target:module /addmodule:Timer.netmodule,
SecondClass.netmodule C:\ClientApp\Module1.vb

Nun liegen drei Bausteine mit jeweils eigenen, typbeschreibenden Metadaten vor. Es fehlt noch die Datei, die das für die Laufzeit unentbehrliche Manifest mit den Metadaten der Assembly enthält. Der VB-Compiler ist nicht mehr dazu geeignet, eine allein stehende Datei zu erzeugen, die nur das Manifest enthält. Jetzt kommt ein anderes Tool des .NET Frameworks zum Einsatz: Es ist die Datei al.exe, die als Assembly Linker bezeichnet wird. Dieses Werkzeug bietet die Möglichkeit, aus einem oder mehreren Codemodulen beziehungsweise Ressourcendateien ein Manifest zu generieren. Wie schon der VB-Compiler, so wartet auch das AL-Tool mit einer Reihe von Optionsschaltern auf. Die meisten haben für unsere Zielsetzung jedoch keine Bedeutung.

Die komplette Anweisung, die uns die Brücke von den Modulen zu der Datei mit dem Manifest baut, ist schon ausgesprochen lang und passt nicht mehr in eine Buchzeile. Wenn Sie das Beispiel nachvollziehen wollen, schreiben Sie die folgende Anweisung bitte in eine Zeile:


al /main:Module1.Main /out:MultiFile.exe /target:exe
Module1.netmodule Timer.netmodule SecondClass.netmodule

Dem Assembly Linker müssen alle NETMODULE-Dateien bekannt gegeben werden, aus denen sich die Assembly zusammensetzt. In unserem Fall handelt es sich um:

gp  Module1.netmodule
gp  Timer.netmodule
gp  SecondClass.netmodule

Beachten Sie bitte, dass alle aufgeführten Dateien durch ein Leerzeichen getrennt werden.

Die Ausführung der Assembly muss an einem ganz bestimmten Punkt starten – in unserem Beispiel ist es die Methode Main im Modul. Das kann unsere Assemblierung natürlich nicht wissen, wir müssen ihr das ausdrücklich mitteilen. Dazu dient die Option /main, hinter der, getrennt durch einen Doppelpunkt, die Klasse mit der Startmethode angegeben wird.

Mit /out können Sie den Namen der Assembly frei bestimmen. Diese Angabe ist nicht optional, Sie müssen sie in jedem Fall beim Aufruf des Assembly Linkers machen. /target spezifiziert das Dateiformat. Es kommen nur drei Formate in Frage: exe für eine Konsolenanwendung, win für eine Windows-basierte Anwendung und lib für eine Codebibliothek.


Galileo Computing

14.1.4 Private und globale Assemblies  downtop

Typen, die in einer Assemblierung enthalten sind, stehen grundsätzlich allen .NET-Anwendungen zur Verfügung, solange sie öffentlich definiert sind. Ist in einer Anwendung ein Verweis auf eine andere Assembly gelegt, können deren Dienste in Anspruch genommen werden.

Wie der Aufruf durch die Common Language Runtime zur Laufzeit erfolgt, ist damit allerdings noch nicht beschrieben, denn die Laufzeit weiß zwischen zwei grundsätzlich unterschiedlichen Assemblies zu unterscheiden: den privaten und den globalen. Mit dem .NET Framework 2.0 wurden darüber hinaus auch noch die befreundeten Assemblies eingeführt.

Private Assemblies

Angenommen Sie haben eine Klassenbibliothek entwickelt und kompiliert. Nehmen wir weiter an, der Name sei MyLibrary. Das Kompilat ist eine DLL-Datei, also eine Assembly (hier: MyLibrary.dll). Sie können auf die in dieser Assembly enthaltenen öffentlichen Typen zugreifen, wenn Sie in einer anderen Anwendung unter Verweise die Datei MyLibrary.dll angeben. Kompilieren Sie die Anwendung, wird im Ausgabeverzeichnis des Compilers nicht nur die Programmdatei (angenommen, sie heißt MyApp.exe) der Anwendung abgelegt, sondern gleichzeitig auch noch die Assembly MyLibrary.dll.

In einem Weitergabeszenario müssen Sie dem Endanwender beide Dateien bereitstellen, da die ausführbare Datei von der Assembly abhängt. Der Endanwender steht dann seinerseits in der Pflicht, MyLibrary.dll und MyApp.exe im gleichen physikalischen Verzeichnis zu installieren, damit die Anwendung fehlerfrei ausgeführt werden kann.

Nehmen wir nun weiter an, der Endanwender sei selbst .NET-Entwickler. Da er im Besitz von MyLibrary.dll ist, kann er in seinen Projekten auf die von Ihnen entwickelte Assembly verweisen und darauf zugreifen. Gibt er seine eigene Anwendung weiter, muss er neben der Programmdatei natürlich ebenfalls die Assembly MyLibrary.dll ausliefern.

In unseren fiktiven Annahmen spielen jetzt schon zwei Anwendungen eine Rolle, die beide dieselbe Assembly benutzen. Jede der beiden Anwendungen greift zur Laufzeit auf die Assembly MyLibrary.dll zu, die sich im jeweiligen Anwendungsverzeichnis befindet. Solche Assemblies werden als private Assemblies bezeichnet und sind der Standard. Das wesentliche Charakteristikum einer privaten Assembly ist, dass sie nur von einer Anwendung gesehen werden kann und benutzt wird.

Wir wollen das Gedankenspiel nun auf die Spitze treiben und annehmen, dass es mehrere Anwendungen gibt, die sich die Dienste von MyLibrary.dll sichern. Die Vorstellung, dass sich später ein Fehler in der Assembly herausstellt, ist nicht ganz abwegig. Wie sieht jetzt das Szenario aus, in dem die fehlerhafte MyLibrary.dll gegen eine neue Version ausgetauscht wird? Wenn auf einem Rechner mehrere Anwendungen installiert sind, die auf die Assembly zurückgreifen, müssen alle Abhängigleiten ausgetauscht werden. Wird eine übersehen, arbeitet die entsprechende Anwendung weiterhin fehlerhaft. Eigentlich ist das eine unzumutbare Situation.

Besser wäre es, MyLibrary.dll zentral zu installieren, denn dann könnten alle Anwendungen zuerst gemeinsam auf die alte, später auf die neue Version zugreifen. Hier setzen die globalen Assemblies an und bieten eine adäquate Lösung des Problems.

Globale Assemblies (Signierung mit starken Namen)

Eine gemeinsam genutzte Assembly, auch als globale Assembly bezeichnet, stellt ihre Dienste allen .NET-Anwendungen des Systems zur Verfügung. Das beste Beispiel globaler Assemblies sind die Klassen des .NET Frameworks. Die Entscheidung, ob eine Assembly global zur Verfügung stehen soll, muss schon bei der Kompilierung berücksichtigt werden, weil standardmäßig immer eine private Assembly erzeugt wird.

Gemeinsam genutzte Assemblies werden in einem speziellen Verzeichnis installiert: dem Global Assembly Cache (GAC). Der GAC ist eine Speicherlokalität, in der sogar mehrere Versionen derselben Assembly installiert werden dürfen und die unter Windows XP im Verzeichnis

\Windows\assembly

zu finden ist. In der Abbildung 14.7 sehen Sie die Anzeige des GAC im Explorer.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.7     Der »Global Assembly Cache« (GAC)

Der GAC ist so strukturiert, dass für jede eingetragene Assembly ein eigener Unterordner angelegt wird und für jede Version darin ein weiterer. Assemblies, die im GAC gespeichert sind, werden von der Common Language Runtime zwar benutzt, allerdings nicht in der Verweisliste der Entwicklungsumgebung angeboten.

Der Windows Explorer lässt es nicht zu, tiefer in das Verzeichnis \Windows\assembly »hineinzuschauen«. Sie können jedoch mit den alten DOS-Befehlen an der Eingabekonsole ohne weiteres die Struktur des GAC erkunden (siehe dazu auch Abbildung 14.8). Ausgehend von der Konsole lassen sich sogar die im GAC eingetragenen Dateien kopieren und stehen damit allgemein zur Verfügung.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.8     Ein Blick in das Innere des Global Assembly Caches

Die Common Language Runtime startet mit den zur Verfügung stehenden Information die Suche nach der entsprechenden Assembly im GAC. Findet die CLR die Assembly, wird sie geladen. War die Suche erfolglos, wird im Anwendungsverzeichnis weitergesucht, weil die CLR dann davon ausgehen muss, dass es sich um eine private handelt. Der Prozess der Suche ist ziemlich kompliziert, weil dabei unter anderem auch noch mögliche Vorgaben in den Konfigurationsdateien eine wichtige Rolle spielen, mit denen wir uns in Kapitel 27 noch beschäftigen werden.

Assemblies, die im GAC installiert sind, weisen gegenüber privaten Assemblies Merkmale auf, die von der CLR ausgewertet werden. Neben dem Namen wird die Identität einer globalen Assembly noch durch

gp  einen Schlüssel
gp  eine Versionsnummer
gp  die erforderliche Kulturinformation

beschrieben. Assemblies, die diese Kriterien erfüllen, werden auch als Assemblies mit starkem Namen (Strong Named Assemblies) bezeichnet. Nur dann, wenn alle vier Kriterien erfüllt sind, lädt die CLR die Assembly aus dem GAC.


Hinweis

Auf die Kulturinformationen werden wir nicht weiter eingehen. Werden diesbezüglich keine besonderen Angaben gemacht, gilt eine Assembly als neutral.


Der öffentliche und der private Schlüssel

Globale Assemblies sind gekennzeichnet durch die Signierung mit einem binären Schlüsselpaar bestehend aus einem öffentlichen und einem privaten Schlüssel. Beide kryptografischen Schlüssel dienen einerseits zur Identifizierung einer Assembly und gewährleisten andererseits bei einer Änderung auch, dass der Autor der neuen Version derselbe ist wie derjenige der alten Version. Nur der Entwickler der Ursprungsversion ist im Besitz von beiden Schlüsseln.

Beim Kompiliervorgang wird ein Teil des öffentlichen Schlüssels (Token) in das Manifest geschrieben und die Datei, die das Manifest enthält, mit dem privaten Schlüssel signiert. Der öffentliche Schlüssel ist ein Teil der Informationen, die eine Clientanwendung zur eindeutigen Identifikation einer bestimmten Assembly benötigt. Der private Schlüssel ist für den Aufrufer bedeutungslos, er sichert aber die Arbeit des Komponentenentwicklers und schützt gleichzeitig vor unbefugter Änderung einer globalen Assemblierung. Privater und öffentlicher Schlüssel korrespondieren miteinander, mit anderen Worten: Zu einem öffentlichen Schlüssel gehört auch ein bestimmter privater – das ist ein wichtiger Aspekt, der in seiner Bedeutung nicht hoch genug eingeschätzt werden darf.

Um eine Assembly mit einem Schlüssel zu signieren, ist eine Schlüsseldatei notwendig, die mit dem Tool sn.exe erzeugt wird.

Versionierung von Assemblies

Der Versionsnummer einer signierten Assembly hat eine ähnlich bedeutende Rolle wie der Schlüssel. Damit wird es möglich, mehrere Versionen einer Assembly auf demselben Computer auszuführen, die sich dann noch nicht einmal im öffentlichen Schlüssel unterscheiden.

Zur Laufzeit ermittelt die Common Language Runtime anhand der Versionsnummer, welche Version einer Assembly von einer Anwendung benutzt werden soll. Standardmäßig wird die Version geladen, die im Manifest der Anwendung angegeben ist. Dieses Verhalten lässt sich mit Hilfe von Konfigurationsdateien beeinflussen, wie in Kapitel 27 noch gezeigt wird.

Die Beschreibung einer Version folgt nach einer festgelegten Spezifikation. Jede Baugruppe hat eine Versionsnummer, die sich aus vier Elementen zusammensetzt, beispielsweise:


1.0.2.2

Die ersten beiden Zahlen beschreiben die Haupt- und Nebenversion. Werden an einer Komponente Änderungen vorgenommen, die inkompatibel zu der Vorgängerversion dieser Komponente sind, beispielsweise durch die Änderung der Parameterliste einer Methode, müssen sich die beiden Komponenten in der Haupt- oder Nebenversionsangabe unterscheiden, z.  B.:


2.0.2.2

Eine abwärtskompatible Änderung wird durch die Elemente Build und Revision beschrieben. Änderungen, die über diese beiden Elemente bekannt gegeben werden, sind nur Korrekturen oder Fehlerbeseitigungen im Programmcode, die auf den Client keinen Einfluss ausüben – zumindest nicht im negativen Sinne, denn normalerweise dürfte ein Client von solchen Änderungen nur profitieren.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.9     Das Schema der Versionierung

Die Versionsnummer wird vor der Kompilierung einer Assembly als Attribut in der Datei AssemblyInfo.vb festgelegt.


<Assembly: AssemblyVersion("1.0.1.0")>

Das Erzeugen einer globalen Assembly

Eine globale Assembly bereitzustellen, erfordert zwei Arbeitsgänge:

1. Der Quellcode wird kompiliert und dabei eine vorher erzeugte oder bereits vorhandene Schlüsseldatei eingebunden. Damit ist die Assembly zur gemeinsamen Nutzung vorbereitet.
       
2. Das Kompilat muss im GAC installiert werden.
       

Wenn wir davon ausgehen, die Assembly an einen Anwender weiterzugeben, benötigen wir zur Erfüllung des zweiten Punktes ein Installationsprogramm, das den Verteilungsprozess automatisiert. Hierzu bietet uns Visual Studio 2005 einen speziellen Projekttyp an, mit dem wir uns jedoch erst im letzten Kapitel dieses Buchs beschäftigen. Alternativ dazu stelle ich Ihnen aber ein Tool des .NET Frameworks vor, mit dem sich die Assembly auch von der Konsole aus im GAC installieren lässt. Auf einer Entwicklermaschine ist das zu Testzwecken vollkommen ausreichend.

Erzeugen der Schlüsseldatei mit dem Tool sn.exe

Eine Assembly im GAC zu installieren setzt einen starken Namen voraus. Das bedeutet, dass die Assembly mit einem Schlüssel signiert werden muss, der in einer Schlüsseldatei bereitgestellt wird. Hierzu stellt uns das .NET Framework mit der Datei sn.exe (sn = strong-named) ein passendes Kommandozeilentool zur Verfügung, das standardmäßig unter

\Programme\Microsoft Visual Studio 8\SDK\v2.0\Bin

zu finden ist.

Gesteuert wird das SN-Tool über diverse Optionsschalter. Der Schalter, mit dem ein Schlüsselpaar generiert wird, lautet -k. Hinter dem Schalter wird der Name der Schlüsseldatei angegeben – möglicherweise auch unter Angabe des Pfades, in dem die Datei gespeichert werden soll. Die Dateierweiterung einer Schlüsseldatei ist in der Regel SNK. Wollen Sie beispielsweise eine Schlüsseldatei namens MyKey.snk erzeugen und diese im Ordner C:\MyProject speichern, muss die Befehlszeile an der Eingabeaufforderung lauten:


sn.exe –k C:\MyProject\MyKey.snk

Das SN-Tool erfüllt nur Aufgaben, die in direktem Zusammenhang mit dem Schlüsselpaar stehen. So extrahiert es unter anderem mit dem Schalter -p aus einem Schlüsselpaar den öffentlichen Teil und speichert ihn in einer separat anzugebenden Datei. Benötigen Sie weitere Informationen zu den Optionsschaltern, schauen Sie bitte in der .NET-Dokumentation nach.


Es ist sinnvoll, die Schlüsseldatei eines Projekt in dem Ordner abzulegen, in dem sich auch die Quellcodedateien befinden. Damit hat man alle projektrelevanten Dateien in einem Ordner und vermeidet das unbeabsichtigte Löschen der Schlüsseldatei. Das hätte nämlich fatale Konsequenzen, wenn später ein Update der Komponente erforderlich werden sollte – Sie könnten sich dann nicht mehr als Autor der Komponente identifizieren, weil Sie eine neue Schlüsseldatei generieren müssten, die dann auch garantiert einen anderen Schlüsselsatz haben wird.


Die Schlüsseldatei mit Visual Studio 2005 erzeugen

Eleganter und einfacher ist das Erzeugen einer Schlüsseldatei, wenn Sie sich dazu Visual Studio 2005 bedienen. Öffnen Sie dazu das Projekteigenschaftsfenster und wählen die Lasche Signierung. Markieren Sie anschließend die Auswahlbox Assembly signieren. Daraufhin wird die Auswahlliste aktiviert, welche die Suche nach einer bereits vorhandenen Schlüsseldatei oder das Erstellen einer neuen ermöglicht. Entscheiden Sie sich für letztgenannte Alternative, geben Sie in einem zusätzlichen Dialogfenster den Schlüsseldateinamen an. Darüber hinaus können Sie die Schlüsseldatei auch mit einem Kennwort schützen.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.10     Schlüsseldatei mit Visual Studio 2005 erzeugen

Beim Signieren einer Assembly haben Sie möglicherweise nicht immer Zugriff auf einen privaten Schlüssel. So kann ein Unternehmen beispielsweise ein stark gesichertes Schlüsselpaar haben, auf das die Entwickler nicht täglich zugreifen können. In diesem Fall müssen Sie eine verzögerte Signierung vornehmen, um zunächst nur den öffentlichen Schlüssel verfügbar zu machen. Markieren Sie hierzu den Optionsschalter Nur verzögerte Signierung. Das Hinzufügen des privaten Schlüssels wird bis zur Bereitstellung der Assembly verschoben.

Die Schlüsseldatei bekannt geben

Signieren Sie unter Zuhilfenahme von Visual Studio 2005 Ihr Projekt, brauchen Sie keine weiteren Maßnahmen mehr zu ergreifen. Die Assembly ist sofort signiert und kann im GAC installiert werden. Nur die Version der Assemblierung sollten Sie, wie gleich beschrieben wird, richtig einstellen.

Anders sieht es aus, wenn Sie mit dem Kommandozeilentool die Schlüsseldatei erzeugt haben. Nun gilt es, diese dem Projekt zuzuordnen. Unterlassen Sie den nachfolgend beschriebenen Schritt, ist die Assemblierung nicht signiert und kann folgerichtig auch nicht als globale Assembly vom GAC aufgenommen werden.

Zum Einbinden der Schlüsseldatei müssen Sie in der Datei AssemblyInfo.vb das Attribut AssemblyKeyFile zusätzlich eintragen, dem der Name der Schlüsseldatei übergeben wird.


<Assembly: AssemblyKeyFile("..\..\MyKey.snk")>

Berücksichtigen Sie, dass bei der Pfadangabe vom Verzeichnis der kompilierten Programmdatei ausgegangen wird. In einem zweiten Attribut, AssemblyVersion, geben Sie die Version der Assemblierung an, z.  B.:


<Assembly: AssemblyVersion("1.0.0.3")>

Diese Angabe wird nach der Installation im GAC des Explorers in der Spalte Version angezeigt. Damit sind alle notwendigen Voraussetzungen geschaffen, um eine Assembly mit starkem Namen über das Menü Erstellen zu kompilieren.

Eine Assembly im GAC installieren

Nach der Kompilierung ist die globale Assembly noch nicht im GAC eingetragen, so dass wir das selbst auf dem Entwicklungsrechner nachholen müssen. Grundsätzlich gibt es dazu zwei Alternativen:

gp  das Kommandozeilenprogramm gacutil des .NET Frameworks
gp  eine Installationsroutine mit dem Micosoft Windows Installer

Die Installationsroutine mit dem Windows Installer wird in Kapitel 27 vorgestellt, so dass wir uns an dieser Stelle dem Kommandozeilentool widmen. Nichtsdestotrotz sollten Sie eine Anwendung, insbesondere dann, wenn eine Assembly im GAC installiert werden soll, nur mit einer benutzergeführten Installationsroutine ausliefern, denn einem Anwender die Ausführung eines Kommandozeilenprogramms aufzubürden, ist schlicht und ergreifend unzumutbar.

Wie alle anderen Tools, so findet man gacutil.exe ebenfalls unter:

\Programme\Microsoft Visual Studio 8\SDK\v2.0\Bin

Die allgemeine Aufrufsyntax lautet:


gacutil [Optionen] [Assemblyname]

Aus der Liste der Optionen ragen zwei besonders heraus: der Schalter /i, um die darauf folgend angegebene Assembly im GAC zu installieren, und der Schalter /u, um eine gemeinsam genutzte Assembly zu deinstallieren, z.  B.:


gacutil /i MyGlobalAssembly.dll

beziehungsweise


gacutil /u MyGlobalAssembly

Noch ein Wort zur Versionierung

Am Anfang des Kapitels habe ich Ihnen erzählt, dass im Manifest einer Assemblierung Informationen darüber stehen, von welchen anderen Komponenten sie abhängt. Dazu gehören unter anderem der Name und die Versionsnummer aller referenzierten Assemblies.

Wird eine referenzierte Assembly gegen eine mit einer höheren Versionsnummer ausgetauscht, ist die Ursache entweder die Bereitstellung zusätzlicher operativer Möglichkeiten oder die Beseitigung eines Bugs. Der Anwender sollte insbesondere im zuletzt aufgeführten Fall die ursprüngliche durch die neue Version ersetzen. Hierbei kommen Konfigurationsdateien ins Spiel, in denen entsprechende Umleitungen festgeschrieben werden können. An dieser Stelle werde ich aber nicht auf die Konfigurationsdateien eingehen und verweise stattdessen auf Kapitel 27, in dem das Thema ausführlich behandelt wird.


Galileo Computing

14.1.5 Festlegen der Assembly-Eigenschaften  toptop

Eben haben ich schon kurz die Datei AssemblyInfo.vb erwähnt. Sie diente dazu, gegebenenfalls hier den Pfad auf die Schlüsseldatei einer signierten Assembly anzugeben. Ganz allgemein dient diese Datei dazu, Zusatzinformationen zu der aktuellen Assemblierung bereitzustellen, beispielsweise eine Beschreibung, Versionsinformationen, Firmenname, Produktname und mehr. Diese werden im Windows Explorer in den Dateieigenschaften angezeigt. Da die Informationen die Assemblierung als Ganzes betreffen, müssen die Deklarationen außerhalb einer Klasse stehen und dürfen auch nur einmal gesetzt werden.


<Assembly: AssemblyTitle("AssemblyInfoDemo")>
<Assembly: AssemblyDescription("Dieses Programm beschreibt
die Einträge in AssemblyInfo.vb")>
<Assembly: AssemblyConfiguration("")>
<Assembly: AssemblyCompany("Tollsoft")>
<Assembly: AssemblyProduct("Visual Basic 2005-Buch")>
<Assembly: AssemblyCopyright("Copyright © Tollsoft 2006")>
<Assembly: AssemblyTrademark("")>
<Assembly: AssemblyCulture("")>
<Assembly: AssemblyVersion("1.0.0.0")>
<Assembly: AssemblyFileVersion("1.0.0.0")>

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.11     Die Eigenschaften einer Assemblierung im Windows Explorer

Sie können die gewünschten Assembly-Informationen in der Datei AssemblyInfo.vb eintragen wie oben gezeigt, Sie können aber auch die Einträge im Eigenschaftsdialog des Projekts vornehmen. Dazu öffnen Sie das Eigenschaftsfenster (oder klicken im Projektmappen-Explorer auf My Project doppelt) und wählen die Lasche Anwendung. Auf dieser Registerkarte sehen Sie die Schaltfläche Assemblyinformation., über die der in der folgenden Abbildung 14.12 gezeigte Dialog geöffnet wird.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 14.12     Eintragen der Assembly-Informationen im Visual Studio 2005

 <<   zurück
  
  Zum Katalog
Zum Katalog: Visual Basic 2005
Visual Basic 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Visual C# 2005






 Visual C# 2005


Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Das Programmierhandbuch SQL Server 2005






 Das Programmier-
 handbuch
 SQL Server 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de